from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver import ActionChains
from selenium.common.exceptions import TimeoutException

import base64
import random
import time
from datetime import datetime

import numpy as np

import skimage
from skimage import io
from skimage.color import rgba2rgb,rgb2gray
from skimage.util import compare_images
from skimage.measure import find_contours
from skimage.filters import threshold_otsu

EMAIL = 'cqc@cuiqingcai.com'
PASSWORD = ''

class GeetestCracker(object):
    def __init__(self):
        # 浏览器
        self.browser = webdriver.Chrome()
        self.wait = WebDriverWait(self.browser, 20)

        # geetest测试页面
        # target_url = "https://www.geetest.com/Register"
        self.target_url = "https://www.geetest.com/demo/slide-bind.html"
        # self.target_url = "http://localhost:5000/"

        self.email = EMAIL
        self.password = PASSWORD

        # 轨迹字典
        # 键：拖动总长度
        # 值：轨迹数组，每个数字是鼠标横向移动一次的像素数
        self.track_map = {}

    def __del__(self):
        self.browser.close()

    def read_track(self):
        """
        获取人工鼠标轨迹记录
        """
        with open("mouse/mouse_track.txt") as f:
            track_data = f.read()
            track_data = track_data.split("\n\n")
            for line in track_data:
                line_list = [int(x) for x in line.split(",")]
                k = line_list[-1]
                self.track_map[k] = np.array(line_list[1:]) - np.array(line_list[:-1])

    def open(self):
        """
        打开网页输入用户名密码
        :return: None
        """
        self.browser.get(self.target_url)
        username = self.wait.until(EC.presence_of_element_located((By.ID, 'username')))
        password = self.wait.until(EC.presence_of_element_located((By.ID, 'password')))
        username.clear()
        username.send_keys(self.email)
        password.send_keys(self.password)

    def open_captcha(self):
        time.sleep(1)
        # 等待按钮可点击
        
        # 测试页面
        submit_btn = self.wait.until(EC.element_to_be_clickable((By.ID, 'btn')))
        # 官网注册页面
        # submit_btn = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_radar_tip')))
        
        # 点击“验证按钮”
        submit_btn.click()

        try:
            # 等待滑块出现
            canvas = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_slicebg')))
            return 0
        except TimeoutException:
            try:
                if submit_btn.text == '验证成功':
                    return 2
                silver = self.wait.until(EC.presence_of_element_located((By.CLASS_NAME, 'geetest_silver')))
                return 1
            except TimeoutException:
            #     radar_tip = self.wait.until(EC.visibility_of_element_located((By.CLASS_NAME, 'geetest_radar_tip')))
                return -1

    def get_image(self):
        # 获取图片地址
        js_getimg_jigsaw = "return document.getElementsByClassName('geetest_canvas_bg')[0].toDataURL('image/png')"
        js_getimg_fullbg = "return document.getElementsByClassName('geetest_canvas_fullbg')[0].toDataURL('image/png')"
        
        # 生成时间+随机数文件名后缀
        suffix = datetime.now().strftime("%Y%m%d%H%M%S") + \
                 "{:>03}".format(random.randint(0,100))
        
        # 获取带有锯齿阴影的图片
        jigsaw_imgpath = self.browser.execute_script(js_getimg_jigsaw)
        jigsaw_imgdata = base64.b64decode(jigsaw_imgpath.split(",")[1])

        self.jigsaw_imgname = "images/jigsaw_" + \
                          suffix + \
                         ".png"

        with open(self.jigsaw_imgname, "wb") as f:
            f.write(jigsaw_imgdata)
            
        # 获取完整图片
        fullbg_imgpath = self.browser.execute_script(js_getimg_fullbg)
        fullbg_imgdata = base64.b64decode(fullbg_imgpath.split(",")[1])

        self.fullbg_imgname = "images/fullbg_" + \
                               suffix + \
                              ".png"

        with open(self.fullbg_imgname, "wb") as f:
            f.write(fullbg_imgdata)

    def compute_offset(self):
        # 读取图片
        img_jigsaw = io.imread(self.jigsaw_imgname)
        img_fullbg = io.imread(self.fullbg_imgname)
        # 灰度化
        img_jigsaw = rgb2gray(rgba2rgb(img_jigsaw))
        img_fullbg = rgb2gray(rgba2rgb(img_fullbg))
        # 求差值
        img_diff = compare_images(img_jigsaw, img_fullbg)
        
        # 二值化
        binary = img_diff > threshold_otsu(img_diff)
        # 最大contour
        contours = find_contours(binary, 0.5)
        # 最左侧横坐标
        self.x_offset = contours[0][:,1].min() - 6

    def gen_track(self):
        # 查找长度最接近的轨迹
        def find_closest():
            return sorted(list(self.track_map.keys()), key=lambda x: abs(self.x_offset-x))[0]
        
        self.track = self.track_map[find_closest()]

    def slide(self):
        # 获取滑块对象
        slider = self.wait.until(EC.element_to_be_clickable((By.CLASS_NAME, 'geetest_slider_button')))

        # 点击并按住
        ActionChains(self.browser).click_and_hold(slider).perform()
        
        # 按照轨迹数组运动
        for x in self.track:
            ActionChains(self.browser).move_by_offset(xoffset=x, yoffset=0).perform()

        time.sleep(0.5)
        # 松开滑块
        ActionChains(self.browser).release().perform()

    def crack(self):
        # 读取人工鼠标轨迹记录
        self.read_track()
        # 输入用户名密码
        self.open()
        # 点击验证按钮
        cap = self.open_captcha()
        if cap == 0:
            # 获取验证码图片
            self.get_image()
            # 计算缺口位移
            self.compute_offset()
            # 获取移动轨迹
            self.gen_track()
            print('滑动轨迹', self.track)
            # 拖动滑块
            self.slide()

            success = self.wait.until(
                EC.text_to_be_present_in_element((By.CLASS_NAME, 'geetest_success_radar_tip_content'), '验证成功'))
            print(success)
        elif cap == 1:
            print('点选图片验证')
        elif cap == 2:
            success = self.wait.until(
                EC.text_to_be_present_in_element((By.CLASS_NAME, 'geetest_success_radar_tip_content'), '验证成功'))
            print(success)

if __name__ == '__main__':
    cracker = GeetestCracker()
    cracker.crack()
